﻿using RabbitMQ.Client;
using System;
using System.Collections.Generic;

namespace RabbitMQ.Extension
{
    public class RabbitProducer
    {
        private RabbitType rabbitType;
        private readonly IModel channel;
        private string queue;
        private string exchange;
        private bool durable;
        public readonly object locker = new object();

        public RabbitProducer(RabbitType rabbitType, IModel channel)
        {
            this.rabbitType = rabbitType;
            this.channel = channel;
        }

        public IModel Channel
        {
            get
            {
                return channel;
            }
        }

        public void Bind(string queue = null, string exchange = null, bool durable = false, bool autoDelete = false, int ttlSecond = 0, string deadLetterExchange = null, string deadLetterRoutingKey = null)
        {
            try
            {
                this.queue = queue;
                this.exchange = exchange;
                this.durable = durable;
                Dictionary<string, object> dict = null;
                if (ttlSecond > 0)
                {
                    dict = new Dictionary<string, object>
                    {
                        { "x-message-ttl",ttlSecond*1000}
                    };
                    if (!string.IsNullOrEmpty(deadLetterExchange))
                    {
                        dict.Add("x-dead-letter-exchange", deadLetterExchange);
                    }
                    if (!string.IsNullOrEmpty(deadLetterRoutingKey))
                    {
                        dict.Add("x-dead-letter-routing-key", deadLetterRoutingKey);
                    }
                }
                if (rabbitType == RabbitType.Work)
                {
                    if (string.IsNullOrEmpty(queue))
                    {
                        throw new Exception("queue can not be null!");
                    }
                    this.exchange = "";
                    channel.QueueDeclare(queue: queue,
                        durable: durable,
                        exclusive: false,
                        autoDelete: autoDelete,
                        arguments: dict);

                    return;
                }

                if (string.IsNullOrEmpty(exchange))
                {
                    throw new Exception("exchange can not be null!");
                }

                if (rabbitType == RabbitType.SubPub)
                {
                    channel.ExchangeDeclare(exchange, "fanout", durable, autoDelete);
                }
                else if (rabbitType == RabbitType.Routing)
                {
                    channel.ExchangeDeclare(exchange, "direct", durable, autoDelete);
                }
                else
                {
                    channel.ExchangeDeclare(exchange, "topic", durable, autoDelete);
                }
            }
            catch (Exception ex)
            {
                channel.Dispose();
                throw ex;
            }

        }

        public void Publish(byte[] data, string routingKey = "", int ttlSecond = 0)
        {
            IBasicProperties properties = null;
            if (durable || ttlSecond > 0)
            {
                properties = channel.CreateBasicProperties();
                if (durable)
                {
                    properties.Persistent = true;
                }
                if (ttlSecond > 0)
                {
                    properties.Expiration = (ttlSecond * 1000).ToString();
                }
            }

            if (rabbitType == RabbitType.Work)
            {
                routingKey = queue;
            }
            else if (rabbitType == RabbitType.SubPub)
            {
                if (routingKey != "")
                {
                    routingKey = "";
                }
            }
            else
            {
                if (string.IsNullOrEmpty(routingKey))
                {
                    throw new Exception("routingKey can not be null!");
                }
            }
            channel.BasicPublish(exchange: exchange,
                 routingKey: routingKey,
                 basicProperties: properties,
                 body: data);
        }

        public bool TryPublish(byte[] data, string routingKey = "", int ttlSecond = 0)
        {
            lock (locker)
            {
                try
                {
                    Publish(data, routingKey, ttlSecond);
                    return true;
                }
                catch
                {
                    return false;
                }
            }

        }

        public bool TryPublishTx(byte[] data, string routingKey = "", int ttlSecond = 0)
        {
            lock (locker)
            {
                try
                {
                    channel.TxSelect();
                    Publish(data, routingKey, ttlSecond);
                    channel.TxCommit();
                    return true;
                }
                catch
                {
                    channel.TxRollback();
                    return false;
                }
            }

        }

        public bool TryPublishConfirm(byte[] data, string routingKey = "", int ttlSecond = 0)
        {
            lock (locker)
            {
                try
                {
                    channel.ConfirmSelect();
                    Publish(data, routingKey, ttlSecond);
                    return channel.WaitForConfirms();
                }
                catch
                {
                    return false;
                }
            }
        }

    }

}
